#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>

#include <sys/time.h>

#include "err.h"
#include "fio_buffer.h"

#define FIO_START    1
#define FIO_PENDING  2
#define FIO_STOP     4

struct fio_data {
	size_t size;
};

#define FIO_FIFO_LEN      512
#define FIO_WAIT_TIMEOUT  30   //unit is ms

static inline void get_mdelay_abstime(struct timespec *abstime, unsigned long ms)
{
	struct timeval delta;

	gettimeofday(&delta, NULL);

	abstime->tv_sec = delta.tv_sec + (ms/1000);
	abstime->tv_nsec = (delta.tv_usec + (ms%1000) * 1000) * 1000;
	if ( abstime->tv_nsec > 1000000000 ) {
		abstime->tv_sec += 1;
		abstime->tv_nsec -= 1000000000;
	}
}

static void *fio_write_thread(void *arg)
{
	struct fio_info *io_info = arg;
	struct fio_data *data = NULL;
	struct ft_fifo  *fifo = io_info->fifo;
	int fd;
	unsigned int len;
	struct timespec abstime;

	pthread_mutex_lock(&io_info->start_write_lock);
	pthread_cond_wait(&io_info->start_wcond, &io_info->start_write_lock);
	pthread_mutex_unlock(&io_info->start_write_lock);
	fd = io_info->fd;

	while((len=ft_fifo_len(fifo)) || !(io_info->status&FIO_STOP)) {
		if(io_info->status & FIO_PENDING) {
			get_mdelay_abstime(&abstime, FIO_WAIT_TIMEOUT);
			pthread_mutex_lock(&io_info->start_write_lock);
			pthread_cond_timedwait(&io_info->start_wcond, &io_info->start_write_lock, &abstime);
			pthread_mutex_unlock(&io_info->start_write_lock);
			continue;
		}

		if(!len) {
			pthread_cond_signal(&io_info->end_wcond);

			get_mdelay_abstime(&abstime, FIO_WAIT_TIMEOUT);
			pthread_mutex_lock(&io_info->start_write_lock);
			pthread_cond_timedwait(&io_info->start_wcond, &io_info->start_write_lock, &abstime);
			pthread_mutex_unlock(&io_info->start_write_lock);
			continue;
		}

		ft_fifo_get(fifo, (unsigned char*)&data, sizeof(void*));
		len = write(fd, (unsigned char*)data+sizeof(*data), data->size);
		free(data);
	};

	pthread_exit(0);
}

void fio_wait_write_stop_and_pending(struct fio_info *io_info)
{
	if(ft_fifo_len(io_info->fifo)) {
		pthread_mutex_lock(&io_info->end_write_lock);
		pthread_cond_wait(&io_info->end_wcond, &io_info->end_write_lock);
		pthread_mutex_unlock(&io_info->end_write_lock);
	}
	io_info->status = FIO_PENDING;
}

void fio_trigger_write(struct fio_info *io_info)
{
	if(!(io_info->status&FIO_START))
		io_info->status = FIO_START;
	pthread_cond_signal(&io_info->start_wcond);
}

ssize_t fio_write(struct fio_info *io_info, const void *buf, size_t count)
{
	struct fio_data *data;

	data = malloc(count+sizeof(data));
	if(!data) {
		perror("fio_write:malloc for fio_data failed!\n");
		return 0;
	}
	
	data->size = count;

	memcpy((unsigned char*)data+sizeof(*data), buf, count);

	if(ft_fifo_put(io_info->fifo, (unsigned char*)&data, sizeof(void*))) {
		if ( pthread_cond_signal(&io_info->start_wcond) != 0 )
			perror("pthread_cond_signal() failed(start_wcond)");
	}
	else {
		printf("ft_fifo_put failed!");
		free(data);
		count = 0;
	}

	return count;
}

struct fio_info *fio_alloc(void)
{
	struct fio_info *io_info;
	int res;
	
	io_info = malloc(sizeof(struct fio_info));
	
	if(!io_info) {
		perror("Memory alloc failed for fio_info.");
		return 0;
	}

	io_info->fifo = ft_fifo_alloc(sizeof(void*)*FIO_FIFO_LEN);
	if(IS_ERR(io_info->fifo)) {
		free(io_info);
		printf("ft_fifo_alloc failed.");
		return 0;
	}

	io_info->status = FIO_START;

	if(pthread_mutex_init(&io_info->start_write_lock, NULL) != 0) {
		perror("Mutex initialization failed!(start_write_lock)\n");
		free(io_info);
		return 0;
	}

	if(pthread_mutex_init(&io_info->end_write_lock, NULL) != 0) {
		perror("Mutex initialization failed!(end_write_lock)\n");
		free(io_info);
		return 0;
	}

	if (pthread_cond_init(&io_info->start_wcond, NULL) < 0 ) {
		free(io_info);
		perror("pthread_cond_init() failed(start_wcond)");
		return 0;
	}
	
	if (pthread_cond_init(&io_info->end_wcond, NULL) < 0 ) {
		free(io_info);
		perror("pthread_cond_init() failed(end_wcond)");
		return 0;
	}

	res = pthread_create(&io_info->thread, NULL, fio_write_thread, (void*)io_info);

	if(res != 0) {
		printf("Thread creation failed.");
		return 0;
	}
	return io_info;
}

void fio_free(struct fio_info *io_info)
{
	int res;
	void *thread_result;

	io_info->status = FIO_STOP;
	pthread_cond_signal(&io_info->start_wcond);
	res = pthread_join(io_info->thread, &thread_result);
	if(res != 0)
		printf("Thread join failed");
	ft_fifo_free(io_info->fifo);
	pthread_cond_destroy(&io_info->start_wcond);
	pthread_cond_destroy(&io_info->end_wcond);
	free(io_info);
}
